#!/usr/bin/python3
# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t -*-

import os
import re
import mrget
import shutil
import pathlib
import hashlib
import subprocess
import mirrors.plugin


class Main:

    def __init__(self):
        self.cfg = mirrors.plugin.params["config"]
        self.dataDir = mirrors.plugin.params["storage-file"]["data-directory"]
        self.runMode = mirrors.plugin.params["run-mode"]

        self.downloadDir = os.path.join(self.dataDir, ".download")
        self.repoDir = os.path.join(self.dataDir, ".repo", "repo")

    def run(self):
        Util.forceMakeDir(self.downloadDir)
        Util.forceMakeDir(os.path.join(self.downloadDir, ".config"))
        Util.forceMakeDir(os.path.join(self.downloadDir, ".config", "git"))
        Util.genGitCfgFile(os.path.join(self.downloadDir, ".gitconfig"), "fake", "fake@fake.com", {})

        envDict = os.environ.copy()
        envDict["REPO_URL"] = mrget.target_urls("mirror://repo")[0]
        envDict["HOME"] = self.downloadDir

        if self.runMode == "init":
            print("Initializing:")
            Util.forceDelete(os.path.join(self.dataDir, ".repo"))
            subprocess.check_call(["repo", "init", "--mirror", "--platform", "all", "-u", "https://aosp.tuna.tsinghua.edu.cn/platform/manifest"], env=envDict, cwd=self.dataDir)        # FIXME: this url should be changed to not-local-mirror

        # FIXME: should use monthly tar file to speed up initialization
        # self.aospDir = os.path.join(self.downloadDir, "aosp")
        # if self.runMode == "init":
        #     dstFile = os.path.join(self.downloadDir, "aosp-latest.tar")
        #     dstMd5File = dstFile + ".md5"

        #     # delete redundant files
        #     if os.path.exists(self.downloadDir):
        #         Util.deleteDirContent(self.downloadDir, [dstFile, dstMd5File])
        #     Util.deleteDirContent(self.dataDir, [self.downloadDir])

        #     # check if download is needed
        #     if os.path.exists(dstFile) and os.path.exists(dstMd5File):
        #         print("Verify \"%s\"." % (dstFile))
        #         bDownload = not Util.verifyFileByMd5file(dstFile, dstMd5File)
        #     else:
        #         bDownload = True

        #     # find or download tar data file
        #     if bDownload:
        #         dstFileUrl = os.path.join(mrget.target_urls("mirror://aosp-monthly")[0], "aosp-latest.tar")
        #         dstMd5FileUrl = dstFileUrl + ".md5"

        #         Util.forceMakeDir(self.downloadDir)

        #         print("Download \"%s\"." % (dstFileUrl))
        #         Util.wgetDownload(dstFileUrl, dstFile)

        #         print("Download \"%s\"." % (dstMd5FileUrl))
        #         Util.wgetDownload(dstMd5FileUrl, dstMd5File)

        #         print("Verify \"%s\"." % (dstFile))
        #         if not Util.verifyFileByMd5file(dstFile, dstMd5File):
        #             raise Exception("the downloaded file is corrupt")

        #     # extract
        #     print("Extract \"%s\"." % (dstFile))
        #     subprocess.check_call(["tar", "-xf", dstFile, "-C", self.downloadDir])

        print("Synchronizing:")
        if True:
            # we use system git config, we don't use any user's git config (aka global git config) by specifying addtional environment variables
            # we manually update self.repoDir to lastest version so that our modification won't be overwritten by "repo sync"
            # subprocess.check_call(["git", "reset", "--hard"], env=envDict, cwd=self.repoDir)
            # subprocess.check_call(["git", "pull"], env=envDict, cwd=self.repoDir)

            # # we export git repositories, we don't need the checked out directories
            # Util.repoDisableCheckout(self.repoDir)
            # # modify the code of repo tool, so that it can use GIT_CONFIG_GLOBAL when finding gitconfig file
            # Util.repoFixGitConfigFileLocation(self.repoDir)
            # user "mirrors" has no home directory, so we modify the code of repo tool, making it not show 'curl: (26) .netrc error: no such file' error
            # Util.repoMakeNetrcOptional(self.repoDir)

            # we use system git config, we don't use any user's git config (aka global git config) by specifying addtional environment variables
            # repo outputs nothing when stdout/stderr is a file, sucks
            subprocess.check_call(["repo", "sync"], env=envDict, cwd=self.dataDir)

        # create symlinks in self.dataDir
        # print("Create symlinks.")
        # for fn in os.listdir(os.path.join(self.aospDir, ".repo", "projects")):
        #     os.symlink(os.path.join(os.path.relpath(self.aospDir, self.dataDir), ".repo", "projects", fn), os.path.join(self.dataDir, fn))

        print("All done.")


class Util:

    @staticmethod
    def forceDelete(path):
        if os.path.islink(path):
            os.remove(path)
        elif os.path.isfile(path):
            os.remove(path)
        elif os.path.isdir(path):
            shutil.rmtree(path)
        elif os.path.lexists(path):
            os.remove(path)             # other type of file, such as device node
        else:
            pass                        # path does not exist, do nothing

    @classmethod
    def forceMakeDir(cls, path):
        if not os.path.exists(path):
            os.mkdir(path)
        elif os.path.islink(path) or not os.path.isdir(path):
            cls.forceDelete(path)
            os.mkdir(path)
        else:
            pass

    @classmethod
    def deleteDirContent(cls, path, fullfnIgnoreList=[]):
        for fn in os.listdir(path):
            fullfn = os.path.join(path, fn)
            if fullfn in fullfnIgnoreList:
                continue
            cls.forceDelete(fullfn)

    @staticmethod
    def replaceInFile(path, s, t):
        buf = pathlib.Path(path).read_text()
        if s not in buf:
            raise Exception("\"%s\" is outdated" % (path))
        if isinstance(s, re.Pattern):
            buf = re.sub(s, t, buf)
        else:
            buf = buf.replace(s, t)
        pathlib.Path(path).write_text(buf)

    @classmethod
    def verifyFileByMd5file(cls, filename, md5file):
        buf = pathlib.Path(md5file).read_text()         # file content example: "87dc57fcd2b7a54eec77e1ad3887a823  aosp-latest.tar"
        md5sum = buf.split(" ")[0]
        return cls.verifyFileByMd5sum(filename, md5sum)

    @staticmethod
    def verifyFileByMd5sum(filename, md5sum):
        with open(filename, "rb") as f:
            thash = hashlib.md5()
            while True:
                block = f.read(65536)
                if len(block) == 0:
                    break
                thash.update(block)
            return thash.hexdigest() == md5sum

    @staticmethod
    def wgetDownload(url, localFile):
        # wget is forbidden when downloading monthly tar
        userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36"
        subprocess.check_call(["wget", "--user-agent=\"%s\"" % (userAgent), "-O", localFile, url])

    @staticmethod
    def genGitCfgFile(path, user, email, urlMap):
        buf = ""

        buf += "[user]\n"
        buf += "    name = %s\n" % (user)
        buf += "    email = %s\n" % (email)

        buf += "[safe]\n"
        buf += "    directory = *\n"

        for k, v in urlMap.items():
            buf += "[url \"%s\"]\n" % (v)
            buf += "    insteadof = %s\n" % (k)

        pathlib.Path(path).write_text(buf)
        return path

    @staticmethod
    def repoFixGitConfigFileLocation(repoDir):
        fullfn = os.path.join(repoDir, "git_config.py")
        s = 'return os.path.expanduser("~/.gitconfig")'
        t = 'return os.getenv("GIT_CONFIG_GLOBAL", os.path.expanduser("~/.gitconfig"))'
        Util.replaceInFile(fullfn, s, t)

    @staticmethod
    def repoMakeNetrcOptional(repoDir):
        fullfn = os.path.join(repoDir, "project.py")
        s = ', "--netrc"'                               # full line: cmd = ["curl", "--fail", "--output", tmpPath, "--netrc", "--location"]
        Util.replaceInFile(fullfn, s, '')

    @staticmethod
    def repoDisableCheckout(repoDir):
        fullfn = os.path.join(repoDir, "subcmds", "sync.py")
        s = "\n        err_checkout = not self._Checkout(\n"
        t = "\n        all_projects = []" + s
        Util.replaceInFile(fullfn, s, t)


# class CallRepo:

#     def __init__(self, dataDir, tmpHomeDir, bUseSystemGitConfig):
#         self._dataDir = dataDir
#         self._tmpHomeDir = tmpHomeDir
#         self._bUseSystemGitConfig = bUseSystemGitConfig

#     def prepare(self):
#         Util.forceMakeDir(self._tmpHomeDir)
#         Util.forceMakeDir(os.path.join(self._tmpHomeDir, ".config"))
#         Util.forceMakeDir(os.path.join(self._tmpHomeDir, ".config", "git"))
#         Util.genGitCfgFile(os.path.join(self._tmpHomeDir, ".gitconfig"), "fake", "fake@fake.com", {})

#         self._envDict = os.environ.copy()
#         self._envDict["REPO_URL"] = mrget.target_urls("mirror://repo")[0]
#         self._envDict["HOME"] = self._tmpHomeDir

#     def repoInit(self, *args):
#         Util.forceDelete(os.path.join(self.dataDir, ".repo"))
#         subprocess.check_call(["repo", "init", *args], env=self._envDict, cwd=self._dataDir)

#     def repoSync(self, *args):
#         assert self._bUseSystemGitConfig

#         repoDir = os.path.join(self._dataDir, ".repo", "repo")

#         # we use system git config, we don't use any user's git config (aka global git config) by specifying addtional environment variables
#         # we manually update self.repoDir to lastest version so that our modification won't be overwritten by "repo sync"
#         subprocess.check_call(["git", "reset", "--hard"], env=self._envDict, cwd=repoDir)
#         subprocess.check_call(["git", "pull"], env=self._envDict, cwd=repoDir)

#         # user "mirrors" has no home directory, so we modify the code of repo tool, making it not show 'curl: (26) .netrc error: no such file' error
#         if False:
#             fullfn = os.path.join(repoDir, "project.py")
#             s = ', "--netrc"'                               # full line: cmd = ["curl", "--fail", "--output", tmpPath, "--netrc", "--location"]
#             Util.replaceInFile(fullfn, s, '')

#         # we use system git config, we don't use any user's git config (aka global git config) by specifying addtional environment variables
#         # repo outputs nothing when stdout/stderr is a file, sucks
#         subprocess.check_call(["repo", "sync"], env=self._envDict, cwd=self._dataDir)


###############################################################################

if __name__ == "__main__":
    Main().run()
